﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Principal;
using NHibernate;
using NHibernate.Linq;
using Xenta.Entities;
using Xenta.Events;
using Xenta.Operations;
using Xenta.Security;
using Xenta.Structs;
using Xenta.Utils;
using log4net;

namespace Xenta.Tests
{
    public class EnvironmentMock : IEnvironment
    {
        public string WorkingDir
        {
            get
            {
                return Directory.GetCurrentDirectory();
            }
        }

        public string DataDir
        {
            get
            {
                return Directory.GetCurrentDirectory();
            }
        }

        public string BinDir
        {
            get
            {
                return Directory.GetCurrentDirectory();
            }
        }

        public WindowsIdentity WorkingIdentity
        {
            get
            {
                return WindowsIdentity.GetCurrent();
            }
        }
    }

    public class UnitOfWorkMock : IUnitOfWork
    {
        private readonly ISessionFactory _factory;
        private ISession _sfSession;
        private ITransaction _sfTransaction;
        private IStatelessSession _slSession;
        private ITransaction _slTransaction;

        public UnitOfWorkMock(ISessionFactory factory)
        {
            if(factory == null)
                throw new ArgumentNullException("factory");

            _factory = factory;
            _sfSession = null;
            _sfTransaction = null;
            _slSession = null;
            _slTransaction = null;
        }

        public IInfrastructure Infrastructure
        {
            get;
            set;
        }

        public void Commit()
        {
            try
            {
                if(_sfTransaction != null)
                    _sfTransaction.Commit();
                if(_slTransaction != null)
                    _slTransaction.Commit();
            }
            finally
            {
                if(_sfTransaction != null)
                {
                    _sfTransaction.Dispose();
                    _sfTransaction = null;
                }
                if(_slTransaction != null)
                {
                    _slTransaction.Dispose();
                    _slTransaction = null;
                }
            }
        }

        public void Flush()
        {
            if(_sfSession == null)
                return;
            _sfSession.Flush();
        }

        public void Rollback()
        {
            try
            {
                if(_sfTransaction != null)
                    _sfTransaction.Rollback();
                if(_slTransaction != null)
                    _slTransaction.Rollback();
            }
            finally
            {
                if(_sfTransaction != null)
                {
                    _sfTransaction.Dispose();
                    _sfTransaction = null;
                    _sfSession.Dispose();
                    _sfSession = null;
                }
                if(_slTransaction != null)
                {
                    _slTransaction.Dispose();
                    _slTransaction = null;
                    _slSession.Dispose();
                    _slSession = null;
                }
            }
        }

        public object Create<TEntity>(TEntity entity, bool stateless)
            where TEntity : IEntity
        {
            BeginWork(stateless);
            if(stateless)
                return _slSession.Insert(entity);
            else
                return _sfSession.Save(entity);
        }

        public void Update<TEntity>(TEntity entity, bool stateless)
            where TEntity : IEntity
        {
            BeginWork(stateless);
            if(stateless)
                _slSession.Update(entity);
            else
                _sfSession.Update(entity);
        }

        public void Delete<TEntity>(TEntity entity, bool stateless)
            where TEntity : IEntity
        {
            BeginWork(stateless);
            if(stateless)
                _slSession.Delete(entity);
            else
                _sfSession.Delete(entity);
        }

        public TEntity Get<TEntity>(object entityID, bool stateless)
            where TEntity : IEntity
        {
            BeginWork(stateless);
            if(stateless)
                return _slSession.Get<TEntity>(entityID);
            else
                return _sfSession.Get<TEntity>(entityID);
        }

        public TEntity Load<TEntity>(object entityID)
            where TEntity : IEntity
        {
            BeginWork(false);
            return _sfSession.Load<TEntity>(entityID);
        }

        public void Refresh<TEntity>(TEntity entity, bool stateless)
            where TEntity : IEntity
        {
            BeginWork(stateless);
            if(stateless)
                _slSession.Refresh(entity);
            else
                _sfSession.Refresh(entity);
        }

        public TEntity Merge<TEntity>(TEntity entity)
            where TEntity : class, IEntity
        {
            BeginWork(false);
            return _sfSession.Merge(entity);
        }

        public IQueryable<TEntity> Query<TEntity>(bool stateless)
            where TEntity : IEntity
        {
            BeginWork(stateless);
            if(stateless)
                return _slSession.Query<TEntity>();
            else
                return _sfSession.Query<TEntity>();
        }

        public IQuery OrmQuery(string queryString, bool stateless)
        {
            BeginWork(stateless);
            if(stateless)
                return _slSession.CreateQuery(queryString);
            else
                return _sfSession.CreateQuery(queryString);
        }

        public ISQLQuery SqlQuery(string queryString, bool stateless = false)
        {
            BeginWork(stateless);
            if(stateless)
                return _slSession.CreateSQLQuery(queryString);
            else
                return _sfSession.CreateSQLQuery(queryString);
        }

        public void Suspend()
        {
            if(_sfTransaction != null || _slTransaction != null)
                throw new Exception("The unit cannot be suspended without explicit commit/rollback.");

            if(_sfSession != null && _sfSession.IsConnected)
                _sfSession.Disconnect();
            if(_slSession != null && _slSession.IsConnected)
            {
                _slSession.Dispose();
                _slSession = null;
            }
        }

        public void Dispose()
        {
            if(_slTransaction != null)
                _slTransaction.Dispose();

            if(_slSession != null)
            {
                _slSession.Dispose();
                _slSession = null;
            }
            if(_sfTransaction != null)
            {
                _sfTransaction.Dispose();
            }
            if(_sfSession != null)
            {
                _sfSession.Dispose();
                _sfSession = null;
            }
        }

        private void BeginWork(bool stateless)
        {
            if(stateless)
            {
                if(_slSession == null)
                    _slSession = _factory.OpenStatelessSession();
                if(_slTransaction == null)
                    _slTransaction = _slSession.BeginTransaction();
            }
            else
            {
                if(_sfSession == null)
                {
                    _sfSession = _factory.OpenSession();
                    _sfSession.FlushMode = FlushMode.Commit;
                }
                else if(!_sfSession.IsConnected)
                {
                    _sfSession.Reconnect();
                }
                if(_sfTransaction == null)
                    _sfTransaction = _sfSession.BeginTransaction();
            }
        }
    }

    public class UnitOfWorkFactoryMock : IUnitOfWorkFactory
    {
        private readonly ISessionFactory _factory;

        public UnitOfWorkFactoryMock(ISessionFactory factory)
        {
            if(factory == null)
                throw new ArgumentNullException("factory");

            _factory = factory;
        }

        public IUnitOfWork Produce()
        {
            return new UnitOfWorkMock(_factory);
        }

        public void Dispose()
        {
        }
    }

    public class UnitOfWorkPoolMock : IUnitOfWorkPool
    {
        private readonly IUnitOfWorkFactory _factory;

        public UnitOfWorkPoolMock(IUnitOfWorkFactory factory)
        {
            _factory = factory;
        }

        public UnitOfWorkLeasehold Lease(Guid guid)
        {
            return new UnitOfWorkLeasehold(_factory.Produce(), guid);
        }

        public void Return(UnitOfWorkLeasehold leasehold)
        {
            leasehold.UoW.Dispose();
        }

        public void Dispose()
        {
        }
    }

    public class InfrastructureMock : IInfrastructure
    {
        private readonly IDictionary<DependencyInfo, IDependency> _dependencies; 
        private readonly IEnvironment _environment;
        private readonly IUnitOfWorkPool _uowPool;
        private readonly IEventBroker _eventBroker;
        private readonly ISecurityAuditor _securityAuditor;

        public InfrastructureMock(IEnvironment environment, 
            IUnitOfWorkPool uowPool, 
            IEventBroker eventBroker,
            ISecurityAuditor securityAuditor,
            IDictionary<DependencyInfo, IDependency> dependencies)
        {
            _environment = environment;
            _uowPool = uowPool;
            _eventBroker = eventBroker;
            _securityAuditor = securityAuditor;
            _dependencies = dependencies;
        }

        public string Name
        {
            get
            {
                return "test";
            }
        }

        public IEnvironment Env
        {
            get
            {
                return _environment;
            }
        }

        public IUnitOfWorkPool UoWPool
        {
            get
            {
                return _uowPool;
            }
        }

        public IEventBroker EventBroker
        {
            get
            {
                return _eventBroker;
            }
        }

        public ISecurityAuditor SecurityAuditor
        {
            get
            {
                return _securityAuditor;
            }
        }

        public ILog Log
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public IDependencyResolver DependencyResolver()
        {
            return new DependencyResolverMock(_dependencies);
        }

        public bool CheckDependency(string id)
        {
            return !String.IsNullOrWhiteSpace(id) && _dependencies.Any(x => x.Key.ID.Equals(id));
        }

        public bool CheckDependency<TDependency>(string id)
            where TDependency : IDependency
        {
            return CheckDependency(id);
        }

        public bool CheckDependency<TDependency>()
            where TDependency : IDependency
        {
            throw new NotImplementedException();
        }

        public bool CheckDependency(Type t)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<DependencyInfo> ListDependencies<TDependency>()
            where TDependency : IDependency
        {
            throw new NotImplementedException();
        }

        public DependencyInfo? DependencyInfo(string id)
        {
            throw new NotImplementedException();
        }

        public void Dispose()
        {
            _uowPool.Dispose();
        }
    }

    public class DependencyResolverMock : IDependencyResolver
    {
        private readonly IDictionary<DependencyInfo, IDependency> _dependencies;

        public DependencyResolverMock(IDictionary<DependencyInfo, IDependency> dependencies)
        {
            _dependencies = dependencies;
        }

        public IDependency Resolve(string id)
        {
            return _dependencies.SingleOrDefault(x => x.Key.ID.Equals(id)).Value;
        }

        public IDependency Resolve(Type t)
        {
            throw new NotImplementedException();
        }

        public TDependency Resolve<TDependency>()
            where TDependency : IDependency
        {
            throw new NotImplementedException();
        }

        public IEnumerable<IDependency> ResolveAll(Type t)
        {
            throw new NotImplementedException();
        }

        public IEnumerable<TDependency> ResolveAll<TDependency>()
            where TDependency : IDependency
        {
            throw new NotImplementedException();
        }

        public void Dispose()
        {
        }
    }

    public class EventBrokerMock : IEventBroker
    {
        #region Fields

        private readonly List<Subscription> _subscriptions;
        private readonly object _lock;

        #endregion

        #region Ctors

        public EventBrokerMock()
        {
            _subscriptions = new List<Subscription>();
            _lock = new Object();
        }

        #endregion

        #region Methods

        public void Publish<TEvent>(TEvent evt) 
            where TEvent : IEvent
        {
            if(Equals(evt, default(TEvent)))
                return;
            var handlers = Enumerable.Empty<Delegate>();
            lock(_lock)
            {
                if(!_subscriptions.Any())
                    return;
                var evtType = typeof(TEvent);
                var subscription = _subscriptions
                    .Where(x => x.EventType == evtType)
                    .SingleOrDefault(x => 
                        evt.Marker == null || 
                        evt.Marker.Equals(x.EventMarker));
                if(subscription == null)
                    return;
                handlers = subscription.EventHandlers.ToArray();
            }
            if(!handlers.Any())
                return;
            foreach(var handler in handlers.Cast<Action<TEvent>>())
                handler(evt);
        }

        public void Subscribe<TEvent>(Action<TEvent> handler, object marker = null) 
            where TEvent : IEvent
        {
            if(handler == null)
                return;
            lock(_lock)
            {
                var evtType = typeof(TEvent);
                var subscription = _subscriptions
                    .Where(x => x.EventType == evtType)
                    .SingleOrDefault(x =>
                        marker == null ||
                        marker.Equals(x.EventMarker));
                if(subscription == null)
                {
                    subscription = new Subscription
                    {
                        EventType = evtType,
                        EventMarker = marker,
                        EventHandlers = new List<Delegate>()
                    };
                    _subscriptions.Add(subscription);
                }
                subscription.EventHandlers.Add(handler);
            }
        }

        public void Unsubscribe<TEvent>(Action<TEvent> handler, object marker = null) 
            where TEvent : IEvent
        {
            if(handler == null)
                return;
            lock(_lock)
            {
                var evtType = typeof(TEvent);
                var subscription = _subscriptions
                    .Where(x => x.EventType == evtType)
                    .SingleOrDefault(x =>
                        marker == null ||
                        marker.Equals(x.EventMarker));
                if(subscription == null)
                    return;
                subscription.EventHandlers.Remove(handler);
                if(!subscription.EventHandlers.Any())
                    _subscriptions.Remove(subscription);
            }
        }

        public void Dispose()
        {
            lock(_lock)
            {
                _subscriptions.Clear();
            }
        }

        #endregion

        #region Classes

        private class Subscription
        {
            public Type EventType
            {
                get;
                set;
            }

            public object EventMarker
            {
                get;
                set;
            }

            public ICollection<Delegate> EventHandlers
            {
                get;
                set;
            }
        }

        #endregion
    }

    public class OperationProcessorMock : IOperationProcessor
    {
        private readonly IInfrastructure _infrastructure;
        private UnitOfWorkLeasehold _uowLeasehold;

        public OperationProcessorMock(IInfrastructure infrastructure)
        {
            _infrastructure = infrastructure;
            _uowLeasehold = null;
        }

        public void Initialize(IWorkingContext ctx)
        {
            _uowLeasehold = _infrastructure.UoWPool.Lease(ctx.CID);
            _uowLeasehold.UoW.Infrastructure = _infrastructure;
        }

        public void Commit()
        {
            if(_uowLeasehold == null)
                return;
            _uowLeasehold.UoW.Commit();
        }

        public void Rollback()
        {
            if(_uowLeasehold == null) 
                return;
            _uowLeasehold.UoW.Rollback();
        }

        public TOperation Execute<TOperation>(TOperation operation)
            where TOperation : IOperation
        {
            _uowLeasehold.UoW.Execute(operation);
            return operation;
        }

        public void Dispose()
        {
            if(_uowLeasehold == null) 
                return;
            _infrastructure.UoWPool.Return(_uowLeasehold);
            _uowLeasehold = null;
        }
    }

    public class SecurityAuditorMock : ISecurityAuditor
    {
        public bool Audit(IWorkingContext ctx, 
            IUnitOfWork uow, 
            ISecuredOperation operation)
        {
            return true;
        }
    }
}
